/*
    This source file is part of GeNa project.
    Copyright © GeNa project 2008
    For the latest info, see http://code.google.com/p/genaproject/ or  http://lumpyproject.forums-rpg.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Foobar is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. */

#ifndef ACTION_H_INCLUDED
#define ACTION_H_INCLUDED

#include <list>
#include <typeinfo>

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/lambda/construct.hpp>
#include <boost/function.hpp>

#include <boost/type_traits.hpp>
#include <boost/utility/enable_if.hpp>
using namespace boost;
using namespace boost::lambda;

#include "object.h"
#include "singleton.h"
#include "application.h"
#include "map_manager.h"

class ObjectMemberSaverBase
{
protected:
    int id;
public:
    int getObjectId()
    {
        return id;
    }

    virtual void operator()() = 0;
};


template<class T, class U, class cond = void> class ObjectMemberSaver : public ObjectMemberSaverBase
{
private:
    U T::*var;
    int id;

    U val;
public:

    ObjectMemberSaver( U T::*_var, int _id)
    {
        var = _var;
        id = _id;
        T* obj = dynamic_cast<T*>(Object::getObject(id));
        val = obj->*var;
    }

    void operator()()
    {
        T* obj = dynamic_cast<T*>(Object::getObject(id));
        U t = obj->*var;
        dynamic_cast<T*>(Object::getObject(id))->*var = val;
        val = t;
    }
};


template<class T, class U> class ObjectMemberSaver<T,U*, typename enable_if< is_base_of<Object, U> >::type > : public ObjectMemberSaverBase
{
private:
    U* T::*var;
    int id;

    int valId;
public:

    ObjectMemberSaver( U* T::*_var, int _id)
    {
        var = _var;
        id = _id;
        T* obj = dynamic_cast<T*>(Object::getObject(id));

        Object* v = dynamic_cast<Object*>( obj->*var);
        valId = v->getId();
    }

    void operator()()
    {
        T* obj = dynamic_cast<T*>(Object::getObject(id));
        Object* v = dynamic_cast<Object*>( obj->*var);
        int t = v->getId();
        obj->*var = dynamic_cast<U*>(Object::getObject(valId));
        valId = t;
    }
};


template<class T, class U> ObjectMemberSaver<T,U> objectMemberSaver( U T::*var, T* obj)
{
    return ObjectMemberSaver<T,U>( var, obj->getId());
}

class UndoRedo
{
private:
    bool isUndo;
    boost::function0<void> undo, redo;
public:
    UndoRedo(boost::function0<void> _undo, boost::function0<void> _redo)
    {
        undo = _undo;
        redo = _redo;
    }

    void operator()()
    {
        if( isUndo)
            undo();
        else
            redo();
        return;
    }
};

class Action
{
protected:
    typedef std::list<int> listOfInt;
    typedef std::list<Object*> listOfObject;

    std::list<Object*> removedObjectList;
    std::list<Object*> newObjectsCopy;
    std::list<Object*> modifiedObjectsCopy;
    std::list<boost::function0<void> > actionList;

    bool isUndo;

	void saveObject(Object* obj);
	template<class T> void addNewObject(T* obj)
	{
	    Object* copy = obj->copy();
	    copy->toId();
	    newObjectsCopy.push_back(copy);
	}
	void removeObject(Object* obj);
	void saveAllNewObjects();
	void addAction(boost::function0<void> action);
	void addUndoRedoPair(boost::function0<void> undo, boost::function0<void> redo);
	template<class T, class U> void saveObjectMember(U T::*var, int id)
	{
	    actionList.push_back( ObjectMemberSaver<T,U>(var, id));
	}
    virtual void restoreObjectsState();

public:
    Action()
    {
        isUndo = true;
    }

    virtual ~Action()
    {
        std::for_each( removedObjectList.begin(), removedObjectList.end(), bind( delete_ptr(), _1));
        std::for_each( newObjectsCopy.begin(), newObjectsCopy.end(), bind( delete_ptr(), _1));
        std::for_each( modifiedObjectsCopy.begin(), modifiedObjectsCopy.end(), bind( delete_ptr(), _1));
    }

    void operator()()
    {
        restoreObjectsState();
        isUndo = !isUndo;
    }

    virtual std::string getDescription() = 0;
    virtual void registered() { };
};

class ActionManager
{
private:
    std::list<Action*> m_actionList;
    std::list<Action*>::iterator m_iter;

    Action* m_currentAction;

public:
    bool setCurrentAction(Action* action)
    {
        if( m_currentAction)
            return false;
        m_currentAction = action;
        action->registered();
        return true;
    }

    void endCurrentAction()
    {
        if( !m_currentAction)
            return;

        if( m_iter != m_actionList.begin())
            m_actionList.erase( m_actionList.begin(), m_iter);

        m_actionList.push_front(m_currentAction);
        m_currentAction = NULL;
        m_iter = m_actionList.begin();

        if( mapManagerInstance.current->actionMgr == this)
        {
            appInstance->m_frame->m_standard->EnableTool(MainFrame::idMenuUndo, true);
            appInstance->m_frame->m_editMenu->Enable(MainFrame::idMenuUndo, true);

            appInstance->m_frame->m_standard->EnableTool(MainFrame::idMenuRedo, false);
            appInstance->m_frame->m_editMenu->Enable(MainFrame::idMenuRedo, false);
        }

    }

    bool isIdle()
    {
        return !m_currentAction;
    }

	void abortCurrentAction()
	{
	    m_currentAction = NULL;
	}

    int getUndoableActionCount()
    {
        int i=1;
        for( std::list<Action*>::iterator iter = m_actionList.begin(); iter != m_iter; iter++)
            i++;

        return m_actionList.size() - i;
    }

    int getRedoableActionCount()
    {
        int i=0;
        for( std::list<Action*>::iterator iter = m_actionList.begin(); iter != m_iter; iter++)
            i++;

        return i;
    }

    bool canUndo()
    {
        return m_iter != m_actionList.end();
    }

    bool canRedo()
    {
        return m_iter != m_actionList.begin();
    }

	//                                  m_iter
	//                                    ↓
	//m_actionList { … | action k-1 | action k | action k+1 | … }
	//                       ↑            ↑
	//                     redo          undo

	void undo( int n) //Undo n step
	{
	    if( m_currentAction)
            return;

	    for( int i=0; i<n && m_iter != m_actionList.end(); i++, m_iter++)
            (**m_iter)();

        if( !canUndo() && mapManagerInstance.current->actionMgr == this)
        {
            appInstance->m_frame->m_standard->EnableTool(MainFrame::idMenuUndo, false);
            appInstance->m_frame->m_editMenu->Enable(MainFrame::idMenuUndo, false);
        }

        if( canRedo() && mapManagerInstance.current->actionMgr == this)
        {
            appInstance->m_frame->m_standard->EnableTool(MainFrame::idMenuRedo, true);
            appInstance->m_frame->m_editMenu->Enable(MainFrame::idMenuRedo, true);
        }
	}

	void redo( int n) //Redo n step
	{
	    if( m_currentAction)
            return;

	    for( int i=0; i<n && m_iter != m_actionList.begin(); i++)
        {
            m_iter--;
            (**m_iter)();
        }

        if( canUndo() && mapManagerInstance.current->actionMgr == this)
        {
            appInstance->m_frame->m_standard->EnableTool(MainFrame::idMenuUndo, true);
            appInstance->m_frame->m_editMenu->Enable(MainFrame::idMenuUndo, true);
        }

        if( !canRedo() && mapManagerInstance.current->actionMgr == this)
        {
            appInstance->m_frame->m_standard->EnableTool(MainFrame::idMenuRedo, false);
            appInstance->m_frame->m_editMenu->Enable(MainFrame::idMenuRedo, false);
        }
	}

    ActionManager() : m_iter(m_actionList.begin())
    {
        m_currentAction = NULL;
    }

    ~ActionManager()
    {
        if( m_currentAction)
            delete m_currentAction;

        std::for_each( m_actionList.begin(), m_actionList.end(), bind(delete_ptr(), _1));
    }

    static ActionManager & getInstance()
    {
        if( mapManagerInstance.current)
            return *mapManagerInstance.current->actionMgr;
        else
            return *(new ActionManager); //Should not happen
    }

private:
    ActionManager( const ActionManager &);
    ActionManager & operator=( const ActionManager &);
};

#define actionManagerInstance MAKE_SINGLETON_PSEUDO_INSTANCE(ActionManager)

#endif // ACTION_H_INCLUDED
